using System;
using System.Collections.Generic;
using System.Reflection;
using NFreeMarker.Template.Utility;
using System.Linq;

namespace NFreeMarker.Ext.Beans
{
    class MethodUtilities
    {
        internal static readonly Type ObjectClass = typeof (object);

        internal static Type GetMostSpecificCommonType(Type c1, Type c2)
        {
            if (c1 == c2)
            {
                return c1;
            }
    //        if(c2.isPrimitive()) {
    //            if(c2 == Byte.TYPE) c2 = Byte.class;
    //            else if(c2 == Short.TYPE) c2 = Short.class;
    //            else if(c2 == Character.TYPE) c2 = Character.class;
    //            else if(c2 == Integer.TYPE) c2 = Integer.class;
    //            else if(c2 == Float.TYPE) c2 = Float.class;
    //            else if(c2 == Long.TYPE) c2 = Long.class;
    //            else if(c2 == Double.TYPE) c2 = Double.class;
    //        }
            HashSet<Type> a1 = GetAssignables(c1, c2);
            HashSet<Type> a2 = GetAssignables(c2, c1);
            a1.IntersectWith(a2);
            if (a1.Count == 0)
            {
                // Can happen when at least one of the arguments is an interface, as
                // they don't have Object at the root of their hierarchy
                return typeof (object);
            }
            // Gather maximally specific elements. Yes, there can be more than one 
            // thank to interfaces. I.e., if you call this method for String.class 
            // and Number.class, you'll have Comparable, Serializable, and Object as 
            // maximal elements. 
            var max = new List<Type>();
            foreach (Type type in a1)
            {
                bool continueOuter = false;
                for (int index = 0; index < max.Count; index++)
                {
                    Type maxType = max[index];
                    if (IsMoreSpecific(maxType, type))
                    {
                        // It can't be maximal, if there's already a more specific
                        // maximal than it.
                        continueOuter = true;
                        break;
                    }
                    if (IsMoreSpecific(type, maxType))
                    {
                        max.RemoveAt(index--);
                    }
                }
                if (continueOuter)
                {
                    continue;
                }
                // If we get here, no current maximal is more specific than the
                // current class, so it is considered maximal as well
                max.Add(type);
            }
            if (max.Count > 1)
            {
                return ObjectClass;
            }
            return max[0];
        }

        /**
         * Determines whether a type represented by a class object is 
         * convertible to another type represented by a class object using a 
         * method invocation conversion, without matching object and primitive
         * types. This method is used to determine the more specific type when
         * comparing signatures of methods.
         * @return true if either formal type is assignable from actual type, 
         * or formal and actual are both primitive types and actual can be
         * subject to widening conversion to formal.
         */
        internal static bool IsMoreSpecific(Type specific, Type generic)
        {
            // Check for identity or widening reference conversion
            if (generic.IsAssignableFrom(specific))
            {
                return true;
            }
            // Check for widening primitive conversion.
            if (generic.IsPrimitive)
            {
                if (generic == typeof (short) && (specific == typeof (byte)))
                {
                    return true;
                }
                if (generic == typeof (int) && 
                   (specific == typeof (short) || specific == typeof (byte)))
                {
                    return true;
                }
                if (generic == typeof (long) && 
                   (specific == typeof (int) || specific == typeof (short) || 
                    specific == typeof (byte)))
                {
                    return true;
                }
                if (generic == typeof (float) && 
                   (specific == typeof (long) || specific == typeof (int) || 
                    specific == typeof (short) || specific == typeof (byte)))
                {
                    return true;
                }
                if (generic == typeof (double) && 
                   (specific == typeof (float) || specific == typeof (long) || 
                    specific == typeof (int) || specific == typeof (short) || 
                    specific == typeof (byte)))
                {
                    return true; 
                }
            }
            return false;
        }

        private static HashSet<Type> GetAssignables(Type c1, Type c2)
        {
            var s = new HashSet<Type>();
            CollectAssignables(c1, c2, s);
            return s;
        }
        
        private static void CollectAssignables(Type c1, Type c2, HashSet<Type> s)
        {
            if (c1.IsAssignableFrom(c2))
            {
                s.Add(c1);
            }
            Type sc = c1.BaseType;
            if (sc != null)
            {
                CollectAssignables(sc, c2, s);
            }
            Type[] itf = c1.GetInterfaces();
            foreach (Type type in itf)
            {
                CollectAssignables(type, c2, s);
            }
        }

        internal static Type[] GetParameterTypes(MemberInfo member)
        {
            if (member is MethodInfo || member is ConstructorInfo)
            {
                return ((MethodBase) member).GetParameterTypes();
            }
            throw new Exception(); // NOTE: Was RuntimeException
        }

        internal static bool IsVarArgs(MemberInfo member)
        {
            if (member is MethodInfo || member is ConstructorInfo)
            {
                return IsVarArgs(member as MethodBase);
            }
            throw new Exception(); // NOTE: Was RuntimeException
        }

        private static bool IsVarArgs(MethodBase method)
        {
            Type lastParameter = method.GetParameterTypes().LastOrDefault();
            return lastParameter != null &&
                   Attribute.IsDefined(lastParameter, typeof (ParamArrayAttribute));
        }
    }
}